package com.sunny.feign.aop;

import com.sunny.feign.custom.CustomHystrix;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import javax.annotation.PreDestroy;
import java.lang.reflect.Method;
import java.util.concurrent.*;

/**
 * @author Sunny
 * @date 2020/2/24
 */
@Aspect
@Component
public class CustomHystrixAspectj {

    private ExecutorService executor = Executors.newFixedThreadPool(10);
    private volatile Semaphore semaphore;

    @Pointcut("execution(* com.sunny.feign.cloud.*.*(..)) && @annotation(com.sunny.feign.custom.CustomHystrix)")
    public void pointcut() {
    }

    @Around(value = "pointcut() && args(msg)")
    public Object invoke(ProceedingJoinPoint joinPoint, String msg) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        CustomHystrix hystrix = method.getAnnotation(CustomHystrix.class);
        long timeout = hystrix.timeout();
        int semaphore = hystrix.semaphore();

        if (timeout <= 0 && semaphore <= 0) {
            return "timeout 与 semaphore 不可同时小于等于 0 !!!";
        }

        if (semaphore > 0 && this.semaphore == null) {
            this.semaphore = new Semaphore(semaphore);
        }

        Object result = null;

        //只根据超时时间熔断
        if (timeout > 0 && semaphore <= 0) {
            Future<Object> future = executor.submit(() -> {
                Object returnValue = null;
                try {
                    returnValue = joinPoint.proceed(new String[]{msg});
                } catch (Throwable ignore) {
                }
                return returnValue;
            });

            try {
                result = future.get(timeout, TimeUnit.MILLISECONDS);
            } catch (InterruptedException | ExecutionException | TimeoutException e) {
                future.cancel(true);
                result = errorSay(msg);
            }
        } else {
            //根据超时时间与信号量熔断
            try {
                if (this.semaphore.tryAcquire(timeout <= 0 ? 100 : timeout, TimeUnit.MILLISECONDS)) {
                    result = joinPoint.proceed(new String[]{msg});
                    //测试并发
//                    TimeUnit.MILLISECONDS.sleep(1000);
                } else {
                    result = errorSay(msg);
                }
            } finally {
                this.semaphore.release();
            }
        }

        return result;
    }

    private String errorSay(String msg) {
        return "Custom Hystrix Error say " + msg;
    }

    @PreDestroy
    public void destroy() {
        executor.shutdown();
    }
}
